package sample;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.stage.FileChooser;
import sample.matrices.Sudoku;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class SudokuController {
    @FXML
    Button initialButton;

    @FXML
    Label generationLabel;
    @FXML
    Label errorLabel;

    @FXML
    TextField inputTextField;

    @FXML
    AnchorPane anchorPane;

    @FXML
    GridPane gridPane;

    @FXML
    Button returnButton;
    @FXML
    void handleReturn() throws Exception {
        Main.replaceSceneContent("initial_scene.fxml");
    }

    @FXML
    Button openButton;

    static int cost = -1;

    static Sudoku sudokuC = new Sudoku(9);



    static int[][] sudoku = new int[][]{{0, 2, 4, 0, 0, 7, 0, 0, 0},
                                        {6, 0, 0, 0, 0, 0, 0, 0, 0},
                                        {0, 0, 3, 6, 8, 0, 4, 1, 5},
                                        {4, 3, 1, 0, 0, 5, 0, 0, 0},
                                        {5, 0, 0, 0, 0, 0, 0, 3, 2},
                                        {7, 9, 0, 0, 0, 0, 0, 6, 0},
                                        {2, 0, 9, 7, 1, 0, 8, 0, 0},
                                        {0, 4, 0, 0, 9, 3, 0, 0, 0},
                                        {3, 1, 0, 0, 0, 4, 7, 5, 0}};
    static int[][] tempSudoku;
    int generation = 0;
    int state = 0;   //0 is initial
    @FXML
    static Label[][] labels = new Label[9][9];
    @FXML
    public void handleInitial() {
        if(state == 0){
            initialButton.setText("START");
            state = 1;

            sudokuC.setData(sudoku);

            generationLabel.setText("  Generation: "+ generation);
            int err = CalculateNumberOfErrors(sudoku);
            errorLabel.setText("  Error: "+err);

            for(int i=0;i<9;i++){
                for(int j=0;j<9;j++){
                    labels[i][j] = new Label("   " + sudoku[i][j]);
                    if(i<3&&j<3){
                        labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:blue;-fx-font-size:30");
                    }
                    else if(i<6&&j<3){
                        labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:pink;-fx-font-size:30");
                    }
                    else if(j<3){
                        labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:skyblue;-fx-font-size:30");
                    }
                    else if(j<6){
                        if(i<3){
                            labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:indianred;-fx-font-size:30");
                        }
                        else if(i<6){
                            labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:red;-fx-font-size:30");
                        }
                        else {
                            labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:purple;-fx-font-size:30");
                        }
                    }
                    else {
                        if(i<3){
                            labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:brown;-fx-font-size:30");
                        }
                        else if(i<6){
                            labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:darkorange;-fx-font-size:30");
                        }
                        else {
                            labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:gold;-fx-font-size:30");
                        }
                    }
                    int finalI = i;
                    int finalJ = j;
                    labels[i][j].setOnMouseEntered((MouseEvent e) ->{
                        labels[finalI][finalJ].setScaleX(1.1);
                        labels[finalI][finalJ].setScaleY(1.1);
                    });
                    labels[i][j].setOnMouseExited((MouseEvent e) ->{
                        labels[finalI][finalJ].setScaleX(1);
                        labels[finalI][finalJ].setScaleY(1);
                    });
                    labels[i][j].setOnMouseClicked((MouseEvent e) ->{
                        sudoku[finalI][finalJ] = Integer.parseInt(inputTextField.getText());
                        labels[finalI][finalJ].setText("   "+sudoku[finalI][finalJ]);
                        int score = CalculateNumberOfErrors(sudoku);
                        errorLabel.setText("  Error: "+score);
                    });
                }
            }

            for(int i=0;i<9;i++){
                for(int j=0;j<9;j++){
                    gridPane.add(labels[i][j], j, i);
                }
            }
        }
        else if(state == 1){
            state = 2;
            initialButton.setText("STOP");
            int solutionFound = 0;

            while (solutionFound == 0) {
                double decreaseFactor = 0.99;
                int[][] fixedSudoku = new int[9][9];
                int stuckCount = 0;

                for (int i = 0; i < 9; i++) {
                    for (int j = 0; j < 9; j++) {
                        fixedSudoku[i][j] = sudoku[i][j];
                    }
                }

                fixSudokuValue(fixedSudoku);
                int[][][] listOfBlocks = CreateList3x3Blocks();
                tempSudoku = RandomlyFill3x3Blocks(sudoku, listOfBlocks);
                double sigma = CalculateInitialSigma(sudoku, fixedSudoku, listOfBlocks);
                int score = CalculateNumberOfErrors(tempSudoku);
                errorLabel.setText("  Error: "+score);
                int itterations = ChooseNumberOfIterations(fixedSudoku);
                if (score <= 0) {
                    solutionFound = 1;
                }
                while (solutionFound == 0) {
                    int previousScore = score;
                    for (int i = 0; i < itterations; i++) {
                        tempSudoku = ChooseNewState(tempSudoku, fixedSudoku, listOfBlocks, sigma);
                        PrintArray(tempSudoku);
                        generation++;
                        generationLabel.setText("  Generation: "+ generation);
                        int scoreDiff = cost;
                        score += scoreDiff;
                        errorLabel.setText("  Error: "+score);
                        if (score <= 0) {
                            solutionFound = 1;
                            break;
                        }
                    }
                    sigma *= decreaseFactor;
                    if (score <= 0) {
                        solutionFound = 1;
                        break;
                    }
                    if (score >= previousScore) {
                        stuckCount += 1;
                    } else {
                        stuckCount = 0;
                    }
                    if (stuckCount > 80) {
                        sigma += 2;
                    }
                    if (CalculateNumberOfErrors(tempSudoku) == 0) {
                        PrintArray(tempSudoku);
                        break;
                    }
                }
            }
            PrintArray(tempSudoku);
            state = 0;
            initialButton.setText("INITIAL");
        }

    }

    @FXML
    void handleOpen() {
        FileChooser fileChooser = new FileChooser();
        // Set extension filter
        FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(
                "XML files (*.xml)", "*.xml");
        fileChooser.getExtensionFilters().add(extFilter);
        // Show save file dialog
        File file = fileChooser.showOpenDialog(Main.getPrimaryStage());
        if (file != null) {
            loadMatrixDataFromFile(file);
        }
    }

    @FXML
    void handleSaveAs() {
        FileChooser fileChooser = new FileChooser();
        // Set extension filter
        FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(
                "XML files (*.xml)", "*.xml");
        fileChooser.getExtensionFilters().add(extFilter);
        // Show save file dialog
        File file = fileChooser.showSaveDialog(Main.getPrimaryStage());
        if (file != null) {
            // Make sure it has the correct extension
            if (!file.getPath().endsWith(".xml")) {
                file = new File(file.getPath() + ".xml");
            }
            saveSudokuDataToFile(file);
        }
    }

    public void loadMatrixDataFromFile(File file) {
        try {
            JAXBContext context = JAXBContext
                    .newInstance(WrapperSudoku.class);
            Unmarshaller um = context.createUnmarshaller();
            // Reading XML from the file and unmarshalling.
            WrapperSudoku wrapper = (WrapperSudoku) um.unmarshal(file);
            generation = wrapper.getGeneration();
            sudoku = wrapper.getGrids();
            handleInitial();
            // Save the file path to the registry.
            Main.setPersonFilePath(file);

        } catch (Exception e) {
        }
    }

    public void saveSudokuDataToFile(File file) {
        try {
            JAXBContext context = JAXBContext
                    .newInstance(WrapperSudoku.class);
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

            // Wrapping our person data.
            WrapperSudoku wrapper = new WrapperSudoku();
            wrapper.setGeneration(generation);
            wrapper.setGrids(sudoku);


            // Marshalling and saving XML to the file.
            m.marshal(wrapper, file);

            // Save the file path to the registry.
            Main.setPersonFilePath(file);
        } catch (Exception e) { // catches ANY exception

        }
    }



    public static void PrintArray(int[][] array) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                labels[i][j].setText("   "+String.valueOf(array[i][j]));
            }
        }
    }

    public static int[][] fixSudokuValue(int[][] array) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (array[i][j] != 0) {
                    array[i][j] = 1;
                }
            }
        }
        return array;
    }

    static int ChooseNumberOfIterations(int[][] fixed_sudoku) {
        int numberOfItterations = 0;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (fixed_sudoku[i][j] != 0) {
                    numberOfItterations += 1;
                }
            }
        }
        return numberOfItterations;
    }

    static int CalculateNumberOfErrors(int[][] sudoku) {
        int numberOfErrors = 0;
        for (int i = 0; i < 9; i++) {
            numberOfErrors += CalculateNumberOfErrorsRowColumn(i, i, sudoku);
        }

        return (numberOfErrors);
    }

    static int CalculateNumberOfErrorsRowColumn(int row, int column, int[][] sudoku) {
        int[] rowArray = sudoku[row];
        int[] columnArray = new int[sudoku.length];

        for (int i = 0; i < 9; i++) {
            columnArray[i] = sudoku[i][column];
        }
        List<Integer> rowList = new ArrayList<>();
        for (int i = 0; i < rowArray.length; i++) {
            if (!rowList.contains(rowArray[i])) {
                rowList.add(rowArray[i]);
            }
        }
        List<Integer> columnList = new ArrayList<>();
        for (int i = 0; i < columnArray.length; i++) {
            if (!columnList.contains(columnArray[i])) {
                columnList.add(columnArray[i]);
            }
        }
        return 9 - rowList.size() + 9 - columnList.size();
    }

    static int[][][] CreateList3x3Blocks() {
        int[][][] finalListOfBlocks = new int[9][9][2];
        for (int r = 0; r < 9; r++) {
            int[] block1 = new int[3];
            int[] block2 = new int[3];

            for (int i = 0; i < 3; i++) {
                block1[i] = i + 3 * (r % 3);
                block2[i] = i + 3 * (r / 3);
            }

            for (int i = 0; i < 9; i++) {

                finalListOfBlocks[r][i][0] = block1[i / 3];
                finalListOfBlocks[r][i][1] = block2[i % 3];
            }

        }
        return finalListOfBlocks;
    }

    static int[][] RandomlyFill3x3Blocks(int[][] sudoku, int[][][] listOfBlocks) {
        //   for block in listOfBlocks:
        // for box in block:

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (sudoku[listOfBlocks[i][j][0]][listOfBlocks[i][j][1]] == 0) {

                    int[][] tmp = listOfBlocks[i];
                    int[][] currentBlock = new int[3][3];

                    for (int k = 0; k < 3; k++) {
                        for (int l = 0; l < 3; l++) {
                            currentBlock[k][l] = sudoku[tmp[k * 3 + l][0]][tmp[k * 3 + l][1]];
                        }
                    }
                    boolean[] flag = new boolean[10];
                    for (int k = 0; k < 3; k++) {
                        for (int l = 0; l < 3; l++) {
                            flag[currentBlock[k][l]] = true;
                        }
                    }

                    boolean judge = false;
                    int index = -1;
                    for (int k = 1; k <= 9; k++) {
                        if (flag[k] == false) {
                            index = k ;
                            judge = true;
                            break;
                        }
                    }

                    if (judge == true) {
                        sudoku[listOfBlocks[i][j][0]][listOfBlocks[i][j][1]] = index;
                    }

                }
            }
        }
        return sudoku;
    }

    static int SumOfOneBlock(int[][] sudoku, int[][] oneBlock) {
        int finalSum = 0;

        for (int i = 0; i < 9; i++) {
            finalSum += sudoku[oneBlock[i][0]][oneBlock[i][1]];
        }

        return (finalSum);
    }

    static int[][] TwoRandomBoxesWithinBlock(int[][] fixedSudoku, int[][] block) {
        while (true) {
            Random r = new Random();

            int firstIndex = r.nextInt(9);
            int secondIndex = r.nextInt(9);

            while (firstIndex == secondIndex) {
                secondIndex = r.nextInt(9);
            }

            int[] firstBox = block[firstIndex];
            int[] secondBox = block[secondIndex];

            if (fixedSudoku[firstBox[0]][firstBox[1]] != 1
                    && fixedSudoku[secondBox[0]][secondBox[1]] != 1) {
                int[][] result = new int[2][2];
                result[0] = firstBox;
                result[1] = secondBox;
                return result;
            }
        }
    }

    static int[][] FlipBoxes(int[][] sudoku, int[][] boxesToFlip) {
        int[][] proposedSudoku = new int[sudoku.length][sudoku.length];
        for (int i = 0; i < sudoku.length; i++) {
            for (int j = 0; j < sudoku.length; j++) {
                proposedSudoku[i][j] = sudoku[i][j];
            }
        }

        int placeHolder = proposedSudoku[boxesToFlip[0][0]][boxesToFlip[0][1]];
        proposedSudoku[boxesToFlip[0][0]][boxesToFlip[0][1]] =
                proposedSudoku[boxesToFlip[1][0]][boxesToFlip[1][1]];
        proposedSudoku[boxesToFlip[1][0]][boxesToFlip[1][1]] = placeHolder;
        return (proposedSudoku);
    }

    static int[][] ChooseNewState(int[][] currentSudoku, int[][] fixedSudoku,
                                  int[][][] listOfBlocks, double sigma) {

        Random r = new Random();
        int[][] randomBlock = listOfBlocks[r.nextInt(9)];


        int[][] boxesToFlip = TwoRandomBoxesWithinBlock(fixedSudoku, randomBlock);
        int[][] proposedSudoku = FlipBoxes(currentSudoku, boxesToFlip);
        int[][] newSudoku = proposedSudoku;
        int[][] boxesToCheck = boxesToFlip;


        int currentCost = CalculateNumberOfErrorsRowColumn(boxesToCheck[0][0], boxesToCheck[0][1],
                currentSudoku) + CalculateNumberOfErrorsRowColumn(boxesToCheck[1][0],
                boxesToCheck[1][1],
                currentSudoku);
        int newCost = CalculateNumberOfErrorsRowColumn(boxesToCheck[0][0], boxesToCheck[0][1],
                newSudoku) + CalculateNumberOfErrorsRowColumn(boxesToCheck[1][0],
                boxesToCheck[1][1],
                newSudoku);

        int costDifference = newCost - currentCost;
        double rho = Math.exp(-costDifference / sigma);

        double ram = r.nextDouble();
        if (ram < rho) {
            cost = costDifference;
            return newSudoku;
        } else {
            cost = 0;
            return currentSudoku;
        }
    }

    static int[][] ProposedState(int[][] sudoku, int[][] fixedSudoku,
                                 int[][][] listOfBlocks) {
        Random r = new Random();
        int[][] randomBlock = listOfBlocks[r.nextInt(9)];


        int[][] boxesToFlip = TwoRandomBoxesWithinBlock(fixedSudoku, randomBlock);
        int[][] proposedSudoku = FlipBoxes(sudoku, boxesToFlip);
        return proposedSudoku;
    }

    static double CalculateInitialSigma(int[][] sudoku, int[][] fixedSudoku, int[][][]
            listOfBlocks) {
        int[] listOfDifferences = new int[9];
        int[][] tmpSudoku = sudoku;
        for (int i = 0; i <9 ; i++) {
            tmpSudoku = ProposedState(tmpSudoku, fixedSudoku, listOfBlocks);
            listOfDifferences[i]=CalculateNumberOfErrors(tmpSudoku);
        }
        int sum = 0;
        for(int i=0;i<listOfDifferences.length;i++){
            sum += listOfDifferences[i];      //求出数组的总和
        }
        //       System.out.println(sum);  //939
        double average = sum/listOfDifferences.length;  //求出数组的平均数
        //       System.out.println(average);   //52.0
        int total=0;
        for(int i=0;i<listOfDifferences.length;i++){
            total += (listOfDifferences[i]-average)*(listOfDifferences[i]-average);   //求出方差
        }
        double standardDeviation = Math.sqrt(total/listOfDifferences.length);   //求出标准差
        return standardDeviation;
    }

}
